#include <QQmlContext>

#include "QQuickROChannel.h"
#include "ROPlayer.h"
#include "ROHost.h"
#include "QQuickROData.h"
#include "ROComponent.h"
#include "ROSyncProperty.h"
#include "QList"




class ROHostPrivate
{
public:
    ROHostPrivate(ROHost* _Q):_Q(_Q){

    }

    void initializeObject(ROSyncObject* obj){
        auto syncObj = qobject_cast<ROSyncObject*>(obj);

        if(syncObj)
        {
            syncObj->initialize();
        }
    }
    //当有新的对象产生时，向所有客户端广播创建信息
    void updateCreation(const GenerateDataPtr& gen){
        if(m_channelServer)
        {
            auto creationMessage = makeCreationMessage({gen});
            auto propertyMessage = makePropertyMessage(gen->creationinfos.keys());
            for(auto i:m_channelServer->channels())
            {
                i->write("roNewObject",creationMessage);
                i->write("roPropertyUpdate",propertyMessage);
            }
        }
    }

    //当有对象被析构时，向所有客户端广播析构信息
    void updateDeletion(ObjectId id){
        if(m_channelServer)
        {
            for(auto i:m_channelServer->channels())
            {
                i->write("roRemoveObject",id);
            }
        }
    }

    QVariant makePropertyMessage(const QList<ObjectId>& objIds){
        QVariantList ret;
        for(auto i:objIds){


            auto data = m_objs.value(i);

            if(!data) continue;

            auto sharedObj = qobject_cast<ROSyncProperty*>(data->obj);

            if(!sharedObj) continue;

            ret << makePropertyMessage(sharedObj,sharedObj->allProperties());
        }
        return ret;
    }

    QVariant makePropertyMessage(ROSyncProperty* obj,const QStringList& props){
        QVariantMap message;
        QVariant propsValues = obj->extractProperties(props);
        convertForNet(propsValues);

        return QVariantMap{{"id",getROObjectData(obj)->id},{"props",propsValues}};
    }

    void convertForNet(QVariant& value){
        convertValueForNet(value,[=](QObject* obj){

                auto data = getROObjectData(obj);
        if(!data)
        {
            return NullId;
        }

        return data->id;
        });
    }

    void convertFromNet(QVariant& value){
        convertValueFromNet(value,[=](ObjectId id)->QObject*{
            if(m_objs.contains(id))
            {
                return m_objs[id]->obj;
            }

            return nullptr;
        });
    }

    QString getObjectId(QObject* obj)
    {
        QQmlContext* ctx = qmlContext(obj);

            if(!ctx)
            {
                return "";
            }

            QString name = ctx->nameForObject(obj);

            return name;
    }

    QVariant makeCreationMessage(const QList<GenerateDataPtr>& objs){
        QVariantList ret;

        for(auto i:objs)
        {
            QVariantMap message;

            message["reason"] = i->reason;
            message["createIds"] = QVariant::fromValue(i->creationinfos);
            message["root"] = i->root;

            ret << message;
        }

        return ret;
    }

    QQuickROChannelServer* m_channelServer = nullptr;
    IdGenerator<quint16> playerIdGen;

    IdGenerator<quint32> m_ObjIdGen = 0;

    QHash<ObjectId,ObjectDataPtr> m_objs;
    QList<GenerateDataPtr> m_gens;

    QSet<ROPlayer*> m_logouts;//已经申请登出的玩家
    QSet<ROPlayer*> m_kicked;//已经踢出的玩家

    ROHost* _Q;
};

ROHost::ROHost():_P(new ROHostPrivate(this))
{

}

ROHost::~ROHost()
{
    setActive(false);
    delete _P;
}

bool ROHost::active() const
{
    return m_active;
}

void ROHost::setActive(bool newActive)
{
    if (m_active == newActive)
        return;
    m_active = newActive;
    validate();
    emit activeChanged();
}

int ROHost::port() const
{
    return m_port;
}

void ROHost::setPort(int newPort)
{
    if (m_port == newPort)
        return;
    m_port = newPort;
    emit portChanged();
}

QQmlComponent *ROHost::playerDelegate() const
{
    return m_playerDelegate;
}

void ROHost::setPlayerDelegate(QQmlComponent *newPlayerDelegate)
{
    if (m_playerDelegate == newPlayerDelegate)
        return;
    m_playerDelegate = newPlayerDelegate;
    emit playerDelegateChanged();
}

QList<ROPlayer *> ROHost::players() const
{
    return m_players;
}

void ROHost::setPlayers(const QList<ROPlayer *> &newPlayers)
{
    if (m_players == newPlayers)
        return;
    m_players = newPlayers;
    emit playersChanged();
}

void ROHost::registerObject(QObject *obj,QStringList reason)
{

    auto objs = obj->findChildren<QObject*>();
    objs.prepend(obj);
    QMap<ObjectId,QObject*> objMap;
    QList<ROSyncObject*> syncObjs;
    for(const auto& i:objs)
    {

        ObjectDataPtr data = std::make_shared<ObjectData>();

        data->host = this;
        data->id = _P->m_ObjIdGen.alloc();
        data->obj = i;

        objMap[data->id] = data->obj;

        _P->m_objs[data->id] = data;

        connect(i,&QObject::destroyed,this,[=](){
            unRegisterObject(i);
        });

        setROObjectData(i,data);

        auto sync = qobject_cast<ROSyncObject*>(i);
        if(sync)
        {
            syncObjs << sync;
        }
    }
    ObjectDataPtr rootdata = getROObjectData(obj);

    GenerateDataPtr genData = std::make_shared<GenerateData>();

    for(const auto &i:objs)
    {
        QVariantMap info;
        ObjectDataPtr data = getROObjectData(i);
        QList<ObjectId> children;
        for(auto j:i->children())
        {
            auto childId = objMap.key(j,0);
            children << childId;
        }
        genData->creationinfos[data->id] = children;
    }

    genData->root = rootdata->id;
    genData->reason = reason;
    _P->m_gens << genData;

    for(auto i:syncObjs)
    {
        _P->initializeObject(i);
    }

    _P->updateCreation({genData});
}

void ROHost::unRegisterObject(QObject *obj)
{
    ObjectDataPtr rootdata = getROObjectData(obj);

    if(!rootdata)
    {
        return;
    }

    auto id = rootdata->id;

    for(int i = 0;i<_P->m_gens.size();i++)
    {
        if(_P->m_gens[i]->root == id)
        {
            _P->m_gens.remove(i);
            break;
        }
    }

    _P->m_objs.remove(id);
    _P->updateDeletion(id);
}

void ROHost::validate()
{
    if(m_active)
    {
        startUp();
    }
    else
    {
        cleanUp();
    }
}

void ROHost::startUp()
{
    if(!m_rootComponent)
    {
        qDebug()<<"无法启动服务端，根组件为空";
    }
    if(m_policy == ListenServer || m_policy == Server)
    {
        //若以监听服务器或纯服务器的方式启动，打开网络端口
        _P->m_channelServer = new QQuickROChannelServer(this);

        connect(_P->m_channelServer,&QQuickROChannelServer::newConnection,this,&ROHost::onNewConnection);

        _P->m_channelServer->listen(QHostAddress::Any,port());
    }

    //创建根组件
    QObject* root = m_rootComponent->create(m_rootComponent->creationContext());
    root->setParent(this);
    registerObject(root,{"root"});//该对象是以根组件的形式创建的。
    setRootObject(root);


    if(m_policy == ListenServer || m_policy == Offline)
    {//若以监听服务器或单机模式的方式启动，自动创建房主Player
        createHostPlayer();
    }
}

void ROHost::cleanUp()
{
    if(_P->m_channelServer)
    {
        for(auto i:_P->m_channelServer->channels())
        {
            i->write("roServerClose",{});
            _P->m_channelServer->deleteLater();
            _P->m_channelServer = nullptr;
        }
    }

    _P->m_gens.clear();
    _P->m_kicked.clear();
    _P->m_logouts.clear();
    while (!_P->m_objs.isEmpty()) {
        delete (*_P->m_objs.begin())->obj;
    }
}

void ROHost::broadcastRPC(int objId, QString name, const QVariantList &param,const QList<ROPlayer*>& players,bool safe)
{
    for(auto i:players)
    {
        if(i->channel)
        {
            i->channel->write("roRPCCall",QVariantMap{{"param",param},{"name",name},{"id",objId}},safe?QQuickROChannel::Safe:QQuickROChannel::Quick);
        }
        else{
            invokeMethod(_P->m_objs.value(objId)->obj,name,param);
        }
    }
}

void ROHost::handleRPC(const QVariantMap &v)
{
    int id = v["id"].toInt();
    QString name = v["name"].toString();
    QVariantList params = v["param"].toList();

    auto data = _P->m_objs.value(id);
    if(!data)
    {
        return;
    }

    invokeMethod(data->obj,name,params);
}

void ROHost::onNewConnection(QQuickROChannel* con)
{
    connect(con,&QQuickROChannel::newMessage,this,[=](QString topic, QVariant value){
        onNewMessage(con,topic,value);
    });

    connect(con,&QQuickROChannel::stateChanged,this,[=](){
        if(con->state() == QQuickROChannel::Disconnected)
        {
            auto player = con->property("player").value<ROPlayer*>();
            if(player)
            {
                if(_P->m_logouts.contains(player))
                {
                    emit player->disconnected(ROPlayer::Logout,QStringLiteral("玩家已退出"));
                }
                else if(_P->m_kicked.contains(player))
                {
                    emit player->disconnected(ROPlayer::Kicked,QStringLiteral("玩家被踢出"));
                }
                else{
                    emit player->disconnected(ROPlayer::LostConnection,QStringLiteral("玩家失去连接"));
                }
                onPlayerRemoved(player);
            }
        }
    });
}

void ROHost::onNewMessage(QQuickROChannel *con, QString topic, QVariant value)
{
    auto param = value.toMap();
    if(topic == "roLogin")//客户端想要发起登录
    {
        onLogin(con,param);
    }
    else if(topic == "roReady")
    {
        auto loginParam = con->property("loginParam").toMap();
        createPlayer(con->property("playerId").toInt(),con,loginParam);
    }
    else if(topic == "roRPCCall")
    {
        handleRPC(param);
    }
    else if(topic == "roLogout")//客户端主动退出
    {
        ROPlayer* player = con->property("player").value<ROPlayer*>();
        _P->m_logouts.insert(player);
        con->disconnect();
    }
}

void ROHost::sendInitializeMessage(QQuickROChannel *con)
{
    int playerId = _P->playerIdGen.alloc();
    con->write("roInit",QVariantMap{
    {"roNewObject",_P->makeCreationMessage(_P->m_gens)},
    {"roPropertyUpdate",_P->makePropertyMessage(_P->m_objs.keys())},
    {"playerId",playerId}
    },QQuickROChannel::Safe);
    con->setProperty("playerId",playerId);
}

void ROHost::createPlayer(int id,QQuickROChannel *con, const QVariantMap &param)
{
    auto player = qobject_cast<ROPlayer*>(m_playerDelegate->createWithInitialProperties(param,m_playerDelegate->creationContext()));
    player->setPlayerId(id);
    player->channel = con;
    player->m_host = this;
    player->setParent(this);
    if(con)
    {
        con->setProperty("player",QVariant::fromValue(player));
    }
    setPlayers(players() << player);
    emit newPlayer(player);
}

void ROHost::onPropertyUpdate(ROSyncProperty *obj, const QStringList &prop,bool safe)
{
    if(_P->m_channelServer)
    {
        for(auto i:_P->m_channelServer->channels())
        {
            i->write("roPropertyUpdate",QVariantList{    _P->makePropertyMessage(obj,prop)},safe?QQuickROChannel::Safe:QQuickROChannel::Quick);
        }
    }
}

void ROHost::onLogin(QQuickROChannel *con,const QVariantMap &param)
{
    if(m_playerValidator&& (m_playerValidator->policy() & ROPlayerValidator::ClientOnly))
    {
        auto ret = m_playerValidator->validate(param);

        if(!ret->accepted())
        {
            con->write("roLoginRejected",ret->rejectMessage());
            con->deleteLater();
            return;
        }
    }

    con->setProperty("loginParam",param["loginParam"]);//暂存一下登录信息
    sendInitializeMessage(con);//发送初始化消息让客户端初始化

}

void ROHost::onPlayerRemoved(ROPlayer *player)
{
    auto players = m_players;
    players.removeAll(player);
    setPlayers(players);

    _P->m_logouts.remove(player);
    _P->m_kicked.remove(player);

    if(player->channel)
    {
        player->channel->deleteLater();
        player->deleteLater();
    }
}

void ROHost::createHostPlayer()
{
    if(m_playerValidator && (m_playerValidator->policy() & ROPlayerValidator::HostOnly))
    {
        auto e = m_playerValidator->validate(m_hostPlayerParam);
        if(!e->accepted())
        {
            qDebug()<<"创建房主玩家失败，验证未通过";
        }
    }

    createPlayer(_P->playerIdGen.alloc(),nullptr,m_hostPlayerParam);
}

void ROHost::kickPlayer(ROPlayer *player,QString reason)
{
    if(player->isHostPlayer())
    {
        emit player->disconnected(ROPlayer::Kicked,reason);
        onPlayerRemoved(player);
    }
    else{
        _P->m_kicked.insert(player);
        player->channel->write(QStringLiteral("roKicked"),QVariantMap{{"msg",reason}});
        player->channel->disconnect();
    }
}

ROPlayerValidator *ROHost::playerValidator() const
{
    return m_playerValidator;
}

void ROHost::setPlayerValidator(ROPlayerValidator *newPlayerValidator)
{
    if (m_playerValidator == newPlayerValidator)
        return;
    m_playerValidator = newPlayerValidator;
    emit playerValidatorChanged();
}

QObject *ROHost::rootObject() const
{
    return m_rootObject;
}

void ROHost::setRootObject(QObject *newRootObject)
{
    if (m_rootObject == newRootObject)
        return;
    m_rootObject = newRootObject;
    emit rootObjectChanged();
}

QQmlComponent *ROHost::rootComponent() const
{
    return m_rootComponent;
}

void ROHost::setRootComponent(QQmlComponent *newRootComponent)
{
    if (m_rootComponent == newRootComponent)
        return;
    m_rootComponent = newRootComponent;
    emit rootComponentChanged();
}

ROHost::Policy ROHost::policy() const
{
    return m_policy;
}

void ROHost::setPolicy(Policy newPolicy)
{
    if (m_policy == newPolicy)
        return;
    m_policy = newPolicy;
    emit policyChanged();
}

QVariantMap ROHost::hostPlayerParam() const
{
    return m_hostPlayerParam;
}

void ROHost::setHostPlayerParam(const QVariantMap &newHostPlayerParam)
{
    if (m_hostPlayerParam == newHostPlayerParam)
        return;
    m_hostPlayerParam = newHostPlayerParam;
    emit hostPlayerParamChanged();
}
